#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <assert.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>

#define USLEEP_TIME 4000000 /* need this to sync stuff */

/* defining my own syscalls and structs because I don't trust your header */
enum sched_reason {
	SCHED_REASON_TASK_CREATE = 1,
	SCHED_REASON_TASK_END = 2,
	SCHED_REASON_TASK_YIELD = 3,
	SCHED_REASON_MQ_OVERDUE = 4,
	SCHED_REASON_TASK_SLEEP = 5,
	SCHED_REASON_PREEMPT = 6,
	SCHED_REASON_TIMESLICE = 7,
	SCHED_REASON_IDLE = 9,
};

struct switch_info {
	int previous_pid;
	int next_pid;
	int previous_policy;
	int next_policy;
	unsigned long time;
	enum sched_reason reason;
};

#ifdef SCHED_MQ
#undef SCHED_MQ
#endif /* SCHED_MQ */
#define SCHED_MQ 4

#define TIMESTAMPS_NUM 10

static int is_mq(int pid)
{
	long __res;
	__asm__ volatile ("movl $243, %%eax;"
			  "movl %1, %%ebx;"
			  "int $0x80;" "movl %%eax,%0":"=m" (__res)
			  :"m"(pid)
			  :"%eax", "%ebx");
	if ((unsigned long)(__res) >= (unsigned long)(-125)) {
		errno = -(__res);
		__res = -1;
	}
	return (int)(__res);
}

static int get_scheduling_statistic(struct switch_info * si)
{
	long __res;
	__asm__ volatile ("movl $244, %%eax;"
			  "movl %1, %%ebx;"
			  "int $0x80;" "movl %%eax,%0":"=m" (__res)
			  :"m"(si)
			  :"%eax", "%ebx");
	if ((unsigned long)(__res) >= (unsigned long)(-125)) {
		errno = -(__res);
		__res = -1;
	}
	return (int)(__res);
}

/* helper funcs for scheduler params */
static int setscheduler(pid_t pid, int policy, int priority, int trials)
{
	/* defining my own sched_params because I don't trust sched.h */
	unsigned int canary1 = 0xDEADBEEF;
	struct sched_params_ {
		int sched_priority;
		int trial_num;
	} lp;
	unsigned int canary2 = 0xCAFEBABE;
	int ret;
	lp.sched_priority = priority;
	lp.trial_num = trials;
	ret = sched_setscheduler(pid, policy, ((struct sched_param *) (&lp)));
	if ((canary1 != 0xDEADBEEF) || (canary2 != 0xCAFEBABE)) {
		/* Not falling for it this time, Anastasia. */
		fprintf(stderr,
		        "!!! setscheduler copied more than it should !!!");
		_exit(1);
	}
	return ret;

}

static int get_nr_trials(pid_t pid, int * nr_trials)
{
	int ret = -1;
	struct sched_params_ {
		int sched_priority;
		int trial_num;
	} lp;
	ret = sched_getparam(pid, (struct sched_param *)&lp);
	*nr_trials = lp.trial_num;
	return ret;
}

static int get_priority(pid_t pid, int * priority)
{
	int ret = -1;
	struct sched_params_ {
		int sched_priority;
		int trial_num;
	} lp;
	ret = sched_getparam(pid, (struct sched_param *)&lp);
	*priority = lp.sched_priority;
	return ret;
}

/* hard work */
static int fibonaci(unsigned int n)
{
	if (n < 2) {
		return n;
	}
	return fibonaci(n-2) + fibonaci(n-1);
}

/* helper functions for printing */
static const char * reason_to_str(enum sched_reason reason)
{
	switch (reason) {
	case SCHED_REASON_TASK_CREATE:
		return "SCHED_REASON_TASK_CREATE";
	case SCHED_REASON_TASK_END:
		return "SCHED_REASON_TASK_END";
	case SCHED_REASON_TASK_YIELD:
		return "SCHED_REASON_TASK_YIELD";
	case SCHED_REASON_MQ_OVERDUE:
		return "SCHED_REASON_MQ_OVERDUE";
	case SCHED_REASON_TASK_SLEEP:
		return "SCHED_REASON_TASK_SLEEP";
	case SCHED_REASON_PREEMPT:
		return "SCHED_REASON_PREEMPT";
	case SCHED_REASON_TIMESLICE:
		return "SCHED_REASON_TIMESLICE";
	case SCHED_REASON_IDLE:
		return "SCHED_REASON_IDLE";
	default:
		return "SCHED_REASON_INVALID";
	}
}

static const char * policy_to_str(int policy)
{
	switch (policy) {
	case SCHED_OTHER:
		return "SCHED_OTHER";
	case SCHED_MQ:
		return "SCHED_MQ";
	case SCHED_RR:
		return "SCHED_RR";
	case SCHED_FIFO:
		return "SCHED_FIFO";
	default:
		return "SCHED_WTF";
	}
}

static const char * errno_to_str(int errno_)
{
	switch(errno_) {
	case EPERM:
		return "EPERM";
	case EINVAL:
		return "EINVAL";
	case ENOMEM:
		return "ENOMEM";
	case ESRCH:
		return "ESRCH";
	default:
		return "E????";
	}
}

static const char * int_to_str(int i)
{
	/* just the interesting cases */
	switch(i) {
	case -1:
		return "-1";
	case 0:
		return "0";
	case 1:
		return "1";
	case 2:
		return "2";
	default:
		return "?";
	}	
}

static const char * bool_to_str(bool b)
{
	switch(b) {
	case true:
		return "OK";
	case false:
		return "FAIL!";
	}
	return "??";
}

static void print_stats()
{
	unsigned int canary1 = 0xCAFEBEEF;
	struct switch_info info[150];
	unsigned int canary2 = 0xDEADBABE;
	int i, ret;

	memset(info, 0xBAADD00D, sizeof(info));

	if (!(ret = get_scheduling_statistic(info))) {
		perror("Can't get stats");
		exit(1);
	}
	if ((canary1 != 0xCAFEBEEF) || (canary2 != 0xDEADBABE)) {
		/* Anti-Anastasia manuever*/
		fprintf(stderr,
		        "!!! get_scheduling_statistic copies wrong! !!!");
		_exit(1);
	}
	if ((ret != 150) && ( *((unsigned int *)(info+ret)) != 0xBAADD00D)) {
		/* Never gonna happen, but for completeness's sake */
		fprintf(stderr,
		        "!!! get_scheduling_statistic is b0rked! !!!");
		_exit(1);
	}
	for (i = 0; i < ret; ++i) {
		printf("%10lu: [%5d|%11s]->[%5d|%11s] = %s\n",
		       info[i].time,
		       info[i].previous_pid,
		       policy_to_str(info[i].previous_policy),
		       info[i].next_pid,
		       policy_to_str(info[i].next_policy),
		       reason_to_str(info[i].reason));
	}
}

/* helper func for fork */
static pid_t fork_and_run(int (*func)(void *), void * args)
{
	pid_t pid = -1;
	switch (pid = fork()) {
	case -1:
		perror("fork_and_run");
		return -1;
	case 0:
		usleep(USLEEP_TIME); /* need time to work */
		exit(func(args));
	default:
		return pid;
	}
}

static int fib_wrap(void * args)
{
	return fibonaci((int) args);
}

static int sleep_wrap(void * args)
{
	sleep((int) args);
	return 0;
}

static int yield_wrap(void * args)
{
	int * share = args;
	*share = sched_yield();
	return 0;
}

struct double_fork_args {
	int (*func)(void *);
	void * args;
	int policy;
	int nr_trials;
	pid_t * pid;
	bool call_function;
};

static int double_fork_setsched(void * foo)
{
	struct double_fork_args * args = foo;
	if (args->call_function)
		args->func(args->args);
	pid_t pid = fork_and_run(args->func, args->args);
	if (args->policy > 0)
		setscheduler(pid, args->policy, 0, args->nr_trials);
	*args->pid = pid;
	return 0;
}

#define SEMANTIC_TEST(name, code, expected, actual, pass)               \
	do { \
		int ret, ret_errno; \
		code \
		ret_errno = errno; \
		printf("%40s: [%6s]?=?[%6s] = %5s\n", \
		       name, expected, actual, bool_to_str(pass)); \
	} while(0)
	
static void semantics_tests()
{
	pid_t * shared_pid = NULL;
	shared_pid = mmap(NULL, sizeof(*shared_pid), PROT_READ|PROT_WRITE,
	                  MAP_SHARED|MAP_ANONYMOUS, -1, 0);
	if (!shared_pid) {
		perror("mmap");
		goto out;
	}
	SEMANTIC_TEST("Set OTHER->MQ",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              ret = setscheduler(pid, SCHED_MQ, 0, 10);,
	              "0", !ret ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 0));
	SEMANTIC_TEST("Set MQ->OTHER",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              setscheduler(pid, SCHED_MQ, 0, 10);
	              ret = setscheduler(pid, SCHED_OTHER, 0, 0);,
	              "EPERM", !ret ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == -1) && (ret_errno == EPERM));
	SEMANTIC_TEST("is_mq on MQ",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              setscheduler(pid, SCHED_MQ, 0, 10);
	              ret = is_mq(pid);,
	              "1", ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 1));
	SEMANTIC_TEST("is_mq on OTHER",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              ret = is_mq(pid);,
	              "EINVAL",
	              ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == -1) && (ret_errno == EINVAL));
	SEMANTIC_TEST("is_mq on MQ overdue",
	              pid_t pid = fork_and_run(fib_wrap, (void *)39);
	              setscheduler(pid, SCHED_MQ, 0, 1);
	              usleep(USLEEP_TIME);
	              ret = is_mq(pid);,
	              "0", ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 0));
	SEMANTIC_TEST("is_mq on double MQ",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              setscheduler(pid, SCHED_MQ, 0, 10);
	              setscheduler(pid, SCHED_MQ, 0, 10);
	              ret = is_mq(pid);,
	              "1", ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 1));
	SEMANTIC_TEST("is_mq on MQ after MQ->OTHER",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              setscheduler(pid, SCHED_MQ, 0, 10);
	              setscheduler(pid, SCHED_OTHER, 0, 10);
	              ret = is_mq(pid);,
	              "1", ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 1));
	SEMANTIC_TEST("is_mq on forked MQ",
	              struct double_fork_args foo;
	              foo.policy = -1;
	              foo.func = sleep_wrap;
	              foo.args = (void *) 2;
	              foo.pid = shared_pid;
	              foo.call_function = false;
	              pid_t pid = fork_and_run(double_fork_setsched, &foo);
	              setscheduler(pid, SCHED_MQ, 0, 10);
	              waitpid(pid, NULL, 0);
	              usleep(USLEEP_TIME);
	              ret = is_mq(*shared_pid);,
	              "1", ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 1));
	SEMANTIC_TEST("is_mq on forked MQ, when it's overdue",
	              struct double_fork_args foo;
	              foo.policy = -1;
	              foo.func = fib_wrap;
	              foo.args = (void *) 37;
	              foo.pid = shared_pid;
	              foo.call_function = false;
	              pid_t pid = fork_and_run(double_fork_setsched, &foo);
	              setscheduler(pid, SCHED_MQ, 0, 1);
	              waitpid(pid, NULL, 0);
	              usleep(USLEEP_TIME);
	              ret = is_mq(*shared_pid);,
	              "0", ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 0));
	SEMANTIC_TEST("is_mq on forked overdue MQ",
	              struct double_fork_args foo;
	              foo.policy = -1;
	              foo.func = fib_wrap;
	              foo.args = (void *) 37;
	              foo.pid = shared_pid;
	              foo.call_function = true;
	              pid_t pid = fork_and_run(double_fork_setsched, &foo);
	              setscheduler(pid, SCHED_MQ, 0, 1);
	              waitpid(pid, NULL, 0);
	              ret = is_mq(*shared_pid);,
	              "0", ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 0));
	SEMANTIC_TEST("is_mq on FIFO",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              setscheduler(pid, SCHED_FIFO, 99, 0);
	              ret = is_mq(pid);,
	              "EINVAL",
	              ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == -1) && (ret_errno == EINVAL));
	SEMANTIC_TEST("set MQ with 0 trials",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              ret = setscheduler(pid, SCHED_MQ, 0, 0);,
	              "EINVAL",
	              ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == -1) && (ret_errno == EINVAL) &&
	              (is_mq(pid) == -1));
	SEMANTIC_TEST("set MQ with -1 trials",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              ret = setscheduler(pid, SCHED_MQ, 0, -1);,
	              "EINVAL",
	              ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == -1) && (ret_errno == EINVAL) &&
	              (is_mq(pid) == -1));
	SEMANTIC_TEST("set MQ with 101 trials",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              ret = setscheduler(pid, SCHED_MQ, 0, 101);,
	              "EINVAL",
	              ret!=-1 ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == -1) && (ret_errno == EINVAL) &&
	              (is_mq(pid) == -1));
	SEMANTIC_TEST("set MQ with 100 trials",
	              pid_t pid = fork_and_run(sleep_wrap, (void *)1);
	              ret = setscheduler(pid, SCHED_MQ, 0, 100);,
	              "0", !ret ? int_to_str(ret) : errno_to_str(ret_errno),
	              (ret == 0));
	SEMANTIC_TEST("yield MQ",
	              pid_t pid = fork_and_run(yield_wrap, shared_pid);
	              setscheduler(pid, SCHED_MQ, 0, 100);
	              waitpid(pid, NULL, 0);
	              ret = *shared_pid;,
	              "-1", int_to_str(ret),
	              (ret == -1));
out:
	munmap(shared_pid, sizeof(*shared_pid));
}
#undef SEMANTIC_TEST /* the epa loves me */

struct timestamp {
	long sec;
	long usec;
	pid_t pid;
	int sched_priority;
	int policy;
	int nr_trials;
};

/* uses a few syscalls, but shouldn't sleep.. */
static void mk_timestamp(struct timestamp * timestamp, int i)
{
	struct timeval tv;
	if (i < 0)
		return;
	assert(timestamp[i].usec == 0);
	gettimeofday(&tv, NULL);
	get_priority(getpid(), &timestamp[i].sched_priority);
	if (timestamp[i].sched_priority == 0) {
		if (is_mq(getpid()) == -1) {
			timestamp[i].policy = SCHED_OTHER;
			timestamp[i].nr_trials = 0;
		} else {
			timestamp[i].policy = SCHED_MQ;
			get_nr_trials(getpid(), &timestamp[i].nr_trials);
		}
	} else {
		timestamp[i].policy = SCHED_FIFO;
		timestamp[i].nr_trials = 0;
	}
	timestamp[i].pid = getpid();
	timestamp[i].sec = tv.tv_sec;
	timestamp[i].usec = tv.tv_usec;
}

static void print_timestamps(struct timestamp * timestamp, int max)
{
	int i;
	printf("+Idx+Timestamp-------------+PID-----+Policy-------+prio-+trials--+\n");
	for (i = 0; i <= max; ++i) {
		printf("[%02d]| @(%10ld:%06ld) | %5d  | %11s | %03d | %6d |\n",
		       i, timestamp[i].sec,
		       timestamp[i].usec, timestamp[i].pid,
		       policy_to_str(timestamp[i].policy),
		       timestamp[i].sched_priority,
		       timestamp[i].nr_trials);
	}
	printf("+---+----------------------+--------+-------------+-----+--------+\n");
}

static bool check_timestamps(struct timestamp * timestamp, int max)
{
	int i;
	long last_sec = timestamp[0].usec;
	long last_usec = timestamp[0].usec;
	for (i = 1; i <= max; ++i) {
		if (last_sec == timestamp[i].sec) {
			if (last_usec > timestamp[i].usec)
				return false;
		} else {
			if (last_sec > timestamp[i].sec)
				return false;
		}
		last_sec = timestamp[i].sec;
		last_usec = timestamp[i].usec;
	}
	return true;
}

struct util {
	struct timestamp * timestamp;
	int before_index;
	int after_index;
	int fib;
};

static int fib_and_timestamp_wrap(void * args)
{
	struct util * foo = args;
	mk_timestamp(foo->timestamp, foo->before_index);
	fibonaci(foo->fib);
	mk_timestamp(foo->timestamp, foo->after_index);
	return 0;
}

static int do_Test1(struct timestamp * timestamps)
{
	struct util foo;
	pid_t pid = -1;
	foo.timestamp = timestamps;
	foo.fib = 36;
	mk_timestamp(timestamps, 0);
	foo.before_index = -1;
	foo.after_index = 4;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 100);
	mk_timestamp(timestamps, 1);
	foo.before_index = 3;
	foo.after_index = -1;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 100);
	mk_timestamp(timestamps, 2);
	return 4;
}

static int do_Test2(struct timestamp * timestamps)
{
	struct util foo;
	pid_t pid = -1;
	foo.timestamp = timestamps;
	foo.fib = 36;
	mk_timestamp(timestamps, 0);
	foo.before_index = -1;
	foo.after_index = 4;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setpriority(PRIO_PROCESS, pid, 5);	
	setscheduler(pid, SCHED_MQ, 0, 100);
	foo.before_index = 6;
	foo.after_index = -1;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 100);
	setpriority(PRIO_PROCESS, pid, 10);
	foo.before_index = 7;
	foo.after_index = 9;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setpriority(PRIO_PROCESS, pid, 11);
	setscheduler(pid, SCHED_MQ, 0, 100);
	foo.before_index = 5;
	foo.after_index = -1;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 100);
	setpriority(PRIO_PROCESS, pid, 9);
	foo.before_index = 2;
	foo.after_index = 3;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_FIFO, 42, 0);

	mk_timestamp(timestamps, 1);
	return 7;
	
}

static int do_Test3(struct timestamp * timestamps)
{
	struct util foo;
	pid_t pid = -1;
	foo.timestamp = timestamps;
	foo.fib = 36;
	mk_timestamp(timestamps, 0);
	foo.before_index = 2;
	foo.after_index = 6;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 1);
	usleep(5000);
	foo.before_index = 3;
	foo.after_index = 7;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 1);
	usleep(5000);
	foo.before_index = 4;
	foo.after_index = 8;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 1);
	usleep(5000);
	foo.before_index = 5;
	foo.after_index = 9;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 1);

	mk_timestamp(timestamps, 1);
	return 9;
}

static int do_Test4(struct timestamp * timestamps)
{
	struct util foo;
	pid_t pid = -1;
	foo.timestamp = timestamps;
	foo.fib = 37;
	mk_timestamp(timestamps, 0);
	foo.before_index = -1;
	foo.after_index = 4;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 1);
	foo.before_index = -1;
	foo.after_index = 5;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 1);
	foo.before_index = -1;
	foo.after_index = 2;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);
	setscheduler(pid, SCHED_FIFO, 80, 0);
	foo.before_index = -1;
	foo.after_index = 3;
	pid = fork_and_run(fib_and_timestamp_wrap, &foo);

	mk_timestamp(timestamps, 1);
	return 5;
}

struct fork_util {
	struct util before;
	struct util father;
	struct util child;
};

static int fib_and_fork_wrap(void * args)
{
	struct fork_util * foo = args;
	fib_and_timestamp_wrap(&foo->before);
	if (fork()) {
		fib_and_timestamp_wrap(&foo->father);
		wait(NULL);
	} else {
		fib_and_timestamp_wrap(&foo->child);
	}
	return 0;
}

static int do_Test5(struct timestamp * timestamps)
{
	struct fork_util foo;
	pid_t pid = -1;
	foo.before.timestamp = timestamps;
	foo.father.timestamp = timestamps;
	foo.child.timestamp = timestamps;
	foo.before.fib = 33;
	foo.father.fib = 37;
	foo.child.fib = 37;
	foo.before.before_index = 1;
	foo.before.after_index = 2;
	foo.father.before_index = 4;
	foo.father.after_index = -1;
	foo.child.before_index = 3;
	foo.child.after_index = -1;

	pid = fork_and_run(fib_and_fork_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 20);
	mk_timestamp(timestamps, 0);

	return 4;
}

static int do_Test6(struct timestamp * timestamps)
{
	struct fork_util foo;
	pid_t pid = -1;
	foo.before.timestamp = timestamps;
	foo.father.timestamp = timestamps;
	foo.child.timestamp = timestamps;
	foo.before.fib = 37;
	foo.father.fib = 36;
	foo.child.fib = 36;
	foo.before.before_index = 1;
	foo.before.after_index = 2;
	foo.father.before_index = 3;
	foo.father.after_index = 4;
	foo.child.before_index = 5;
	foo.child.after_index = 6;

	pid = fork_and_run(fib_and_fork_wrap, &foo);
	setscheduler(pid, SCHED_MQ, 0, 1);
	mk_timestamp(timestamps, 0);

	return 4;
}

#define TEST(name, desc)                                                     \
	do { \
		memset(timestamps, 0, sizeof(*timestamps) * TIMESTAMPS_NUM); \
		int max = do_ ## name(timestamps); \
		while(wait(NULL) != -1) {} \
		if (check_timestamps(timestamps, max)) { \
			printf(#name " (%s) Passed!\n", desc); \
			if (is_timestamps) print_timestamps(timestamps, max); \
			if (is_stats) { \
				print_stats(); \
			} \
		} else { \
			printf(#name " (%s) FAILED!\n", desc); \
			print_timestamps(timestamps, max); \
			print_stats(); \
			pass = false; \
		} \
		memset(timestamps, 0, sizeof(*timestamps) * TIMESTAMPS_NUM); \
	} while(0);

int main(int argc, char ** argv)
{
	struct timestamp * timestamps = NULL;
	bool pass = true;
	bool is_timestamps = false;
	bool is_stats = false;
	int ret = 1;
	if (argc != 3) {
		fprintf(stderr, "Usage: %s 0|1 0|1\n", argv[0]);
		fprintf(stderr, "(try %s 0 0)\n", argv[0]);
		goto out;
	}
	is_timestamps = atoi(argv[1]);
	is_stats = atoi(argv[2]);
	timestamps = mmap(NULL, sizeof(*timestamps) * TIMESTAMPS_NUM,
	                  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS,
	                  -1, 0);
	if (!timestamps) {
		perror("main() mmap");
		goto out;
	}
	printf("%s", "This can take a while...\n");
	printf("%s", "Testing syscall semantics:\n");
	semantics_tests();
	if (is_stats) {
		printf("%s", "Printing scheduling statistics:\n");
		print_stats();
	}

	printf("%s", "Running tests:\n");	
	TEST(Test1, "Basic round-robin");
	TEST(Test2, "MQ priorities");
	TEST(Test3, "Overdue MQ fifo");
	TEST(Test4, "Overdue MQ + FIFO&OTHER");
	TEST(Test5, "MQ Forking");
	TEST(Test6, "Overdue MQ forking");
	
	if (pass) {
		printf("All tests passed! =D\n");
		ret = 0;
	} else {
		printf("FAILED! =,(\n");
		ret = 1;
	}
out:
	munmap(timestamps, sizeof(*timestamps) * TIMESTAMPS_NUM);
	return ret;
}

#undef TEST